# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.676.12.7 -> 1.676.12.8 # arch/ia64/hp/sim/Makefile 1.1 -> 1.2 # (new) -> 1.1 arch/ia64/hp/sim/simscsi.c # (new) -> 1.1 arch/ia64/hp/sim/simscsi.h # (new) -> 1.1 arch/ia64/hp/sim/simserial.c # (new) -> 1.1 arch/ia64/hp/sim/simeth.c # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/09/27 bjorn_helgaas@hp.com 1.676.12.8 # ia64: Move simeth, simserial, simscsi to arch/ia64/hp/sim. # -------------------------------------------- # diff -Nru a/arch/ia64/hp/sim/Makefile b/arch/ia64/hp/sim/Makefile --- a/arch/ia64/hp/sim/Makefile Wed Oct 8 09:07:02 2003 +++ b/arch/ia64/hp/sim/Makefile Wed Oct 8 09:07:02 2003 @@ -1,6 +1,8 @@ # # ia64/platform/hp/sim/Makefile # +# Copyright (C) 2002 Hewlett-Packard Co. +# David Mosberger-Tang # Copyright (C) 1999 Silicon Graphics, Inc. # Copyright (C) Srinivasa Thirumalachar (sprasad@engr.sgi.com) # @@ -9,5 +11,9 @@ obj-y := hpsim_console.o hpsim_irq.o hpsim_setup.o obj-$(CONFIG_IA64_GENERIC) += hpsim_machvec.o + +obj-$(CONFIG_HP_SIMETH) += simeth.o +obj-$(CONFIG_HP_SIMSERIAL) += simserial.o +obj-$(CONFIG_HP_SIMSCSI) += simscsi.o include $(TOPDIR)/Rules.make diff -Nru a/arch/ia64/hp/sim/simeth.c b/arch/ia64/hp/sim/simeth.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/arch/ia64/hp/sim/simeth.c Wed Oct 8 09:07:02 2003 @@ -0,0 +1,533 @@ +/* + * Simulated Ethernet Driver + * + * Copyright (C) 1999-2001 Hewlett-Packard Co + * Copyright (C) 1999-2001 Stephane Eranain + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIMETH_RECV_MAX 10 + +/* + * Maximum possible received frame for Ethernet. + * We preallocate an sk_buff of that size to avoid costly + * memcpy for temporary buffer into sk_buff. We do basically + * what's done in other drivers, like eepro with a ring. + * The difference is, of course, that we don't have real DMA !!! + */ +#define SIMETH_FRAME_SIZE ETH_FRAME_LEN + + +#define SSC_NETDEV_PROBE 100 +#define SSC_NETDEV_SEND 101 +#define SSC_NETDEV_RECV 102 +#define SSC_NETDEV_ATTACH 103 +#define SSC_NETDEV_DETACH 104 + +#define NETWORK_INTR 8 + +struct simeth_local { + struct net_device_stats stats; + int simfd; /* descriptor in the simulator */ +}; + +static int simeth_probe1(void); +static int simeth_open(struct net_device *dev); +static int simeth_close(struct net_device *dev); +static int simeth_tx(struct sk_buff *skb, struct net_device *dev); +static int simeth_rx(struct net_device *dev); +static struct net_device_stats *simeth_get_stats(struct net_device *dev); +static void simeth_interrupt(int irq, void *dev_id, struct pt_regs * regs); +static void set_multicast_list(struct net_device *dev); +static int simeth_device_event(struct notifier_block *this,unsigned long event, void *ptr); + +static char *simeth_version="0.3"; + +/* + * This variable is used to establish a mapping between the Linux/ia64 kernel + * and the host linux kernel. + * + * As of today, we support only one card, even though most of the code + * is ready for many more. The mapping is then: + * linux/ia64 -> linux/x86 + * eth0 -> eth1 + * + * In the future, we some string operations, we could easily support up + * to 10 cards (0-9). + * + * The default mapping can be changed on the kernel command line by + * specifying simeth=ethX (or whatever string you want). + */ +static char *simeth_device="eth0"; /* default host interface to use */ + + + +static volatile unsigned int card_count; /* how many cards "found" so far */ +static int simeth_debug; /* set to 1 to get debug information */ + +/* + * Used to catch IFF_UP & IFF_DOWN events + */ +static struct notifier_block simeth_dev_notifier = { + simeth_device_event, + 0 +}; + + +/* + * Function used when using a kernel command line option. + * + * Format: simeth=interface_name (like eth0) + */ +static int __init +simeth_setup(char *str) +{ + simeth_device = str; + return 1; +} + +__setup("simeth=", simeth_setup); + +/* + * Function used to probe for simeth devices when not installed + * as a loadable module + */ + +int __init +simeth_probe (void) +{ + int r; + + printk("simeth: v%s\n", simeth_version); + + r = simeth_probe1(); + + if (r == 0) register_netdevice_notifier(&simeth_dev_notifier); + + return r; +} + +extern long ia64_ssc (long, long, long, long, int); +extern void ia64_ssc_connect_irq (long intr, long irq); + +static inline int +netdev_probe(char *name, unsigned char *ether) +{ + return ia64_ssc(__pa(name), __pa(ether), 0,0, SSC_NETDEV_PROBE); +} + + +static inline int +netdev_connect(int irq) +{ + /* XXX Fix me + * this does not support multiple cards + * also no return value + */ + ia64_ssc_connect_irq(NETWORK_INTR, irq); + return 0; +} + +static inline int +netdev_attach(int fd, int irq, unsigned int ipaddr) +{ + /* this puts the host interface in the right mode (start interupting) */ + return ia64_ssc(fd, ipaddr, 0,0, SSC_NETDEV_ATTACH); +} + + +static inline int +netdev_detach(int fd) +{ + /* + * inactivate the host interface (don't interrupt anymore) */ + return ia64_ssc(fd, 0,0,0, SSC_NETDEV_DETACH); +} + +static inline int +netdev_send(int fd, unsigned char *buf, unsigned int len) +{ + return ia64_ssc(fd, __pa(buf), len, 0, SSC_NETDEV_SEND); +} + +static inline int +netdev_read(int fd, unsigned char *buf, unsigned int len) +{ + return ia64_ssc(fd, __pa(buf), len, 0, SSC_NETDEV_RECV); +} + +/* + * Function shared with module code, so cannot be in init section + * + * So far this function "detects" only one card (test_&_set) but could + * be extended easily. + * + * Return: + * - -ENODEV is no device found + * - -ENOMEM is no more memory + * - 0 otherwise + */ +static int +simeth_probe1(void) +{ + unsigned char mac_addr[ETH_ALEN]; + struct simeth_local *local; + struct net_device *dev; + int fd, i; + + /* + * XXX Fix me + * let's support just one card for now + */ + if (test_and_set_bit(0, &card_count)) + return -ENODEV; + + /* + * check with the simulator for the device + */ + fd = netdev_probe(simeth_device, mac_addr); + if (fd == -1) + return -ENODEV; + + dev = init_etherdev(NULL, sizeof(struct simeth_local)); + if (!dev) + return -ENOMEM; + + memcpy(dev->dev_addr, mac_addr, sizeof(mac_addr)); + + dev->irq = ia64_alloc_irq(); + + /* + * attach the interrupt in the simulator, this does enable interrupts + * until a netdev_attach() is called + */ + netdev_connect(dev->irq); + + memset(dev->priv, 0, sizeof(struct simeth_local)); + + local = dev->priv; + local->simfd = fd; /* keep track of underlying file descriptor */ + + dev->open = simeth_open; + dev->stop = simeth_close; + dev->hard_start_xmit = simeth_tx; + dev->get_stats = simeth_get_stats; + dev->set_multicast_list = set_multicast_list; /* no yet used */ + + /* Fill in the fields of the device structure with ethernet-generic values. */ + ether_setup(dev); + + printk("%s: hosteth=%s simfd=%d, HwAddr", dev->name, simeth_device, local->simfd); + for(i = 0; i < ETH_ALEN; i++) { + printk(" %2.2x", dev->dev_addr[i]); + } + printk(", IRQ %d\n", dev->irq); + + return 0; +} + +/* + * actually binds the device to an interrupt vector + */ +static int +simeth_open(struct net_device *dev) +{ + if (request_irq(dev->irq, simeth_interrupt, 0, "simeth", dev)) { + printk ("simeth: unable to get IRQ %d.\n", dev->irq); + return -EAGAIN; + } + + netif_start_queue(dev); + + return 0; +} + +/* copied from lapbether.c */ +static __inline__ int dev_is_ethdev(struct net_device *dev) +{ + return ( dev->type == ARPHRD_ETHER && strncmp(dev->name, "dummy", 5)); +} + + +/* + * Handler for IFF_UP or IFF_DOWN + * + * The reason for that is that we don't want to be interrupted when the + * interface is down. There is no way to unconnect in the simualtor. Instead + * we use this function to shutdown packet processing in the frame filter + * in the simulator. Thus no interrupts are generated + * + * + * That's also the place where we pass the IP address of this device to the + * simulator so that that we can start filtering packets for it + * + * There may be a better way of doing this, but I don't know which yet. + */ +static int +simeth_device_event(struct notifier_block *this,unsigned long event, void *ptr) +{ + struct net_device *dev = (struct net_device *)ptr; + struct simeth_local *local; + struct in_device *in_dev; + struct in_ifaddr **ifap = NULL; + struct in_ifaddr *ifa = NULL; + int r; + + + if ( ! dev ) { + printk(KERN_WARNING "simeth_device_event dev=0\n"); + return NOTIFY_DONE; + } + + if ( event != NETDEV_UP && event != NETDEV_DOWN ) return NOTIFY_DONE; + + /* + * Check whether or not it's for an ethernet device + * + * XXX Fixme: This works only as long as we support one + * type of ethernet device. + */ + if ( !dev_is_ethdev(dev) ) return NOTIFY_DONE; + + if ((in_dev=dev->ip_ptr) != NULL) { + for (ifap=&in_dev->ifa_list; (ifa=*ifap) != NULL; ifap=&ifa->ifa_next) + if (strcmp(dev->name, ifa->ifa_label) == 0) break; + } + if ( ifa == NULL ) { + printk("simeth_open: can't find device %s's ifa\n", dev->name); + return NOTIFY_DONE; + } + + printk("simeth_device_event: %s ipaddr=0x%x\n", dev->name, htonl(ifa->ifa_local)); + + /* + * XXX Fix me + * if the device was up, and we're simply reconfiguring it, not sure + * we get DOWN then UP. + */ + + local = dev->priv; + /* now do it for real */ + r = event == NETDEV_UP ? + netdev_attach(local->simfd, dev->irq, htonl(ifa->ifa_local)): + netdev_detach(local->simfd); + + printk("simeth: netdev_attach/detach: event=%s ->%d\n", event == NETDEV_UP ? "attach":"detach", r); + + return NOTIFY_DONE; +} + +static int +simeth_close(struct net_device *dev) +{ + netif_stop_queue(dev); + + free_irq(dev->irq, dev); + + return 0; +} + +/* + * Only used for debug + */ +static void +frame_print(unsigned char *from, unsigned char *frame, int len) +{ + int i; + + printk("%s: (%d) %02x", from, len, frame[0] & 0xff); + for(i=1; i < 6; i++ ) { + printk(":%02x", frame[i] &0xff); + } + printk(" %2x", frame[6] &0xff); + for(i=7; i < 12; i++ ) { + printk(":%02x", frame[i] &0xff); + } + printk(" [%02x%02x]\n", frame[12], frame[13]); + + for(i=14; i < len; i++ ) { + printk("%02x ", frame[i] &0xff); + if ( (i%10)==0) printk("\n"); + } + printk("\n"); +} + + +/* + * Function used to transmit of frame, very last one on the path before + * going to the simulator. + */ +static int +simeth_tx(struct sk_buff *skb, struct net_device *dev) +{ + struct simeth_local *local = (struct simeth_local *)dev->priv; + +#if 0 + /* ensure we have at least ETH_ZLEN bytes (min frame size) */ + unsigned int length = ETH_ZLEN < skb->len ? skb->len : ETH_ZLEN; + /* Where do the extra padding bytes comes from inthe skbuff ? */ +#else + /* the real driver in the host system is going to take care of that + * or maybe it's the NIC itself. + */ + unsigned int length = skb->len; +#endif + + local->stats.tx_bytes += skb->len; + local->stats.tx_packets++; + + + if (simeth_debug > 5) frame_print("simeth_tx", skb->data, length); + + netdev_send(local->simfd, skb->data, length); + + /* + * we are synchronous on write, so we don't simulate a + * trasnmit complete interrupt, thus we don't need to arm a tx + */ + + dev_kfree_skb(skb); + return 0; +} + +static inline struct sk_buff * +make_new_skb(struct net_device *dev) +{ + struct sk_buff *nskb; + + /* + * The +2 is used to make sure that the IP header is nicely + * aligned (on 4byte boundary I assume 14+2=16) + */ + nskb = dev_alloc_skb(SIMETH_FRAME_SIZE + 2); + if ( nskb == NULL ) { + printk(KERN_NOTICE "%s: memory squeeze. dropping packet.\n", dev->name); + return NULL; + } + nskb->dev = dev; + + skb_reserve(nskb, 2); /* Align IP on 16 byte boundaries */ + + skb_put(nskb,SIMETH_FRAME_SIZE); + + return nskb; +} + +/* + * called from interrupt handler to process a received frame + */ +static int +simeth_rx(struct net_device *dev) +{ + struct simeth_local *local; + struct sk_buff *skb; + int len; + int rcv_count = SIMETH_RECV_MAX; + + local = (struct simeth_local *)dev->priv; + /* + * the loop concept has been borrowed from other drivers + * looks to me like it's a throttling thing to avoid pushing to many + * packets at one time into the stack. Making sure we can process them + * upstream and make forward progress overall + */ + do { + if ( (skb=make_new_skb(dev)) == NULL ) { + printk(KERN_NOTICE "%s: memory squeeze. dropping packet.\n", dev->name); + local->stats.rx_dropped++; + return 0; + } + /* + * Read only one frame at a time + */ + len = netdev_read(local->simfd, skb->data, SIMETH_FRAME_SIZE); + if ( len == 0 ) { + if ( simeth_debug > 0 ) printk(KERN_WARNING "%s: count=%d netdev_read=0\n", dev->name, SIMETH_RECV_MAX-rcv_count); + break; + } +#if 0 + /* + * XXX Fix me + * Should really do a csum+copy here + */ + memcpy(skb->data, frame, len); +#endif + skb->protocol = eth_type_trans(skb, dev); + + if ( simeth_debug > 6 ) frame_print("simeth_rx", skb->data, len); + + /* + * push the packet up & trigger software interrupt + */ + netif_rx(skb); + + local->stats.rx_packets++; + local->stats.rx_bytes += len; + + } while ( --rcv_count ); + + return len; /* 0 = nothing left to read, otherwise, we can try again */ +} + +/* + * Interrupt handler (Yes, we can do it too !!!) + */ +static void +simeth_interrupt(int irq, void *dev_id, struct pt_regs * regs) +{ + struct net_device *dev = dev_id; + + if ( dev == NULL ) { + printk(KERN_WARNING "simeth: irq %d for unknown device\n", irq); + return; + } + + /* + * very simple loop because we get interrupts only when receving + */ + while (simeth_rx(dev)); +} + +static struct net_device_stats * +simeth_get_stats(struct net_device *dev) +{ + struct simeth_local *local = (struct simeth_local *) dev->priv; + + return &local->stats; +} + +/* fake multicast ability */ +static void +set_multicast_list(struct net_device *dev) +{ + printk(KERN_WARNING "%s: set_multicast_list called\n", dev->name); +} + +#ifdef CONFIG_NET_FASTROUTE +static int +simeth_accept_fastpath(struct net_device *dev, struct dst_entry *dst) +{ + printk(KERN_WARNING "%s: simeth_accept_fastpath called\n", dev->name); + return -1; +} +#endif + +__initcall(simeth_probe); diff -Nru a/arch/ia64/hp/sim/simscsi.c b/arch/ia64/hp/sim/simscsi.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/arch/ia64/hp/sim/simscsi.c Wed Oct 8 09:07:02 2003 @@ -0,0 +1,384 @@ +/* + * Simulated SCSI driver. + * + * Copyright (C) 1999, 2001 Hewlett-Packard Co + * Copyright (C) 1999, 2001 David Mosberger-Tang + * Copyright (C) 1999 Stephane Eranian + * + * 99/12/18 David Mosberger Added support for READ10/WRITE10 needed by linux v2.3.33 + */ +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "scsi.h" +#include "sd.h" +#include "hosts.h" +#include "simscsi.h" + +#define DEBUG_SIMSCSI 1 + +/* Simulator system calls: */ + +#define SSC_OPEN 50 +#define SSC_CLOSE 51 +#define SSC_READ 52 +#define SSC_WRITE 53 +#define SSC_GET_COMPLETION 54 +#define SSC_WAIT_COMPLETION 55 + +#define SSC_WRITE_ACCESS 2 +#define SSC_READ_ACCESS 1 + +#ifdef DEBUG_SIMSCSI + int simscsi_debug; +# define DBG simscsi_debug +#else +# define DBG 0 +#endif + +#if 0 +struct timer_list disk_timer; +#else +static void simscsi_interrupt (unsigned long val); +DECLARE_TASKLET(simscsi_tasklet, simscsi_interrupt, 0); +#endif + +struct disk_req { + unsigned long addr; + unsigned len; +}; + +struct disk_stat { + int fd; + unsigned count; +}; + +extern long ia64_ssc (long arg0, long arg1, long arg2, long arg3, int nr); + +static int desc[8] = { -1, -1, -1, -1, -1, -1, -1, -1 }; + +static struct queue_entry { + Scsi_Cmnd *sc; +} queue[SIMSCSI_REQ_QUEUE_LEN]; + +static int rd, wr; +static atomic_t num_reqs = ATOMIC_INIT(0); + +/* base name for default disks */ +static char *simscsi_root = DEFAULT_SIMSCSI_ROOT; + +#define MAX_ROOT_LEN 128 + +/* + * used to setup a new base for disk images + * to use /foo/bar/disk[a-z] as disk images + * you have to specify simscsi=/foo/bar/disk on the command line + */ +static int __init +simscsi_setup (char *s) +{ + /* XXX Fix me we may need to strcpy() ? */ + if (strlen(s) > MAX_ROOT_LEN) { + printk("simscsi_setup: prefix too long---using default %s\n", simscsi_root); + } + simscsi_root = s; + return 1; +} + +__setup("simscsi=", simscsi_setup); + +static void +simscsi_interrupt (unsigned long val) +{ + unsigned long flags; + Scsi_Cmnd *sc; + + spin_lock_irqsave(&io_request_lock, flags); + { + while ((sc = queue[rd].sc) != 0) { + atomic_dec(&num_reqs); + queue[rd].sc = 0; + if (DBG) + printk("simscsi_interrupt: done with %ld\n", sc->serial_number); + (*sc->scsi_done)(sc); + rd = (rd + 1) % SIMSCSI_REQ_QUEUE_LEN; + } + } + spin_unlock_irqrestore(&io_request_lock, flags); +} + +int +simscsi_detect (Scsi_Host_Template *templ) +{ + templ->proc_name = "simscsi"; +#if 0 + init_timer(&disk_timer); + disk_timer.function = simscsi_interrupt; +#endif + return 1; /* fake one SCSI host adapter */ +} + +int +simscsi_release (struct Scsi_Host *host) +{ + return 0; /* this is easy... */ +} + +const char * +simscsi_info (struct Scsi_Host *host) +{ + return "simulated SCSI host adapter"; +} + +int +simscsi_abort (Scsi_Cmnd *cmd) +{ + printk ("simscsi_abort: unimplemented\n"); + return SCSI_ABORT_SUCCESS; +} + +int +simscsi_reset (Scsi_Cmnd *cmd, unsigned int reset_flags) +{ + printk ("simscsi_reset: unimplemented\n"); + return SCSI_RESET_SUCCESS; +} + +int +simscsi_biosparam (Disk *disk, kdev_t n, int ip[]) +{ + int size = disk->capacity; + + ip[0] = 64; + ip[1] = 32; + ip[2] = size >> 11; + return 0; +} + +static void +simscsi_readwrite (Scsi_Cmnd *sc, int mode, unsigned long offset, unsigned long len) +{ + struct disk_stat stat; + struct disk_req req; + + req.addr = __pa(sc->request_buffer); + req.len = len; /* # of bytes to transfer */ + + if (sc->request_bufflen < req.len) + return; + + stat.fd = desc[sc->target]; + if (DBG) + printk("simscsi_%s @ %lx (off %lx, len %lu) ->", + mode == SSC_READ ? "read":"write", req.addr, offset, len); + ia64_ssc(stat.fd, 1, __pa(&req), offset, mode); + ia64_ssc(__pa(&stat), 0, 0, 0, SSC_WAIT_COMPLETION); + + if (stat.count == req.len) { + sc->result = GOOD; + } else { + sc->result = DID_ERROR << 16; + } + if (DBG) + printk("%d\n", sc->result); +} + +static void +simscsi_sg_readwrite (Scsi_Cmnd *sc, int mode, unsigned long offset) +{ + int list_len = sc->use_sg; + struct scatterlist *sl = (struct scatterlist *)sc->buffer; + struct disk_stat stat; + struct disk_req req; + + stat.fd = desc[sc->target]; + + while (list_len) { + req.addr = __pa(sl->address); + req.len = sl->length; + if (DBG) + printk("simscsi_sg_%s @ %lx (off %lx) use_sg=%d len=%d\n", + mode == SSC_READ ? "read":"write", req.addr, offset, + list_len, sl->length); + ia64_ssc(stat.fd, 1, __pa(&req), offset, mode); + ia64_ssc(__pa(&stat), 0, 0, 0, SSC_WAIT_COMPLETION); + + /* should not happen in our case */ + if (stat.count != req.len) { + sc->result = DID_ERROR << 16; + return; + } + offset += sl->length; + sl++; + list_len--; + } + sc->result = GOOD; +} + +/* + * function handling both READ_6/WRITE_6 (non-scatter/gather mode) + * commands. + * Added 02/26/99 S.Eranian + */ +static void +simscsi_readwrite6 (Scsi_Cmnd *sc, int mode) +{ + unsigned long offset; + + offset = (((sc->cmnd[1] & 0x1f) << 16) | (sc->cmnd[2] << 8) | sc->cmnd[3])*512; + if (sc->use_sg > 0) + simscsi_sg_readwrite(sc, mode, offset); + else + simscsi_readwrite(sc, mode, offset, sc->cmnd[4]*512); +} + + +static void +simscsi_readwrite10 (Scsi_Cmnd *sc, int mode) +{ + unsigned long offset; + + offset = ( (sc->cmnd[2] << 24) | (sc->cmnd[3] << 16) + | (sc->cmnd[4] << 8) | (sc->cmnd[5] << 0))*512; + if (sc->use_sg > 0) + simscsi_sg_readwrite(sc, mode, offset); + else + simscsi_readwrite(sc, mode, offset, ((sc->cmnd[7] << 8) | sc->cmnd[8])*512); +} + +int +simscsi_queuecommand (Scsi_Cmnd *sc, void (*done)(Scsi_Cmnd *)) +{ + char fname[MAX_ROOT_LEN+16]; + char *buf; +#if DEBUG_SIMSCSI + register long sp asm ("sp"); + + if (DBG) + printk("simscsi_queuecommand: target=%d,cmnd=%u,sc=%lu,sp=%lx,done=%p\n", + sc->target, sc->cmnd[0], sc->serial_number, sp, done); +#endif + + sc->result = DID_BAD_TARGET << 16; + sc->scsi_done = done; + if (sc->target <= 7 && sc->lun == 0) { + switch (sc->cmnd[0]) { + case INQUIRY: + if (sc->request_bufflen < 35) { + break; + } + sprintf (fname, "%s%c", simscsi_root, 'a' + sc->target); + desc[sc->target] = ia64_ssc (__pa(fname), SSC_READ_ACCESS|SSC_WRITE_ACCESS, + 0, 0, SSC_OPEN); + if (desc[sc->target] < 0) { + /* disk doesn't exist... */ + break; + } + buf = sc->request_buffer; + buf[0] = 0; /* magnetic disk */ + buf[1] = 0; /* not a removable medium */ + buf[2] = 2; /* SCSI-2 compliant device */ + buf[3] = 2; /* SCSI-2 response data format */ + buf[4] = 31; /* additional length (bytes) */ + buf[5] = 0; /* reserved */ + buf[6] = 0; /* reserved */ + buf[7] = 0; /* various flags */ + memcpy(buf + 8, "HP SIMULATED DISK 0.00", 28); + sc->result = GOOD; + break; + + case TEST_UNIT_READY: + sc->result = GOOD; + break; + + case READ_6: + if (desc[sc->target] < 0 ) + break; + simscsi_readwrite6(sc, SSC_READ); + break; + + case READ_10: + if (desc[sc->target] < 0 ) + break; + simscsi_readwrite10(sc, SSC_READ); + break; + + case WRITE_6: + if (desc[sc->target] < 0) + break; + simscsi_readwrite6(sc, SSC_WRITE); + break; + + case WRITE_10: + if (desc[sc->target] < 0) + break; + simscsi_readwrite10(sc, SSC_WRITE); + break; + + + case READ_CAPACITY: + if (desc[sc->target] < 0 || sc->request_bufflen < 8) { + break; + } + buf = sc->request_buffer; + + /* pretend to be a 1GB disk (partition table contains real stuff): */ + buf[0] = 0x00; + buf[1] = 0x1f; + buf[2] = 0xff; + buf[3] = 0xff; + /* set block size of 512 bytes: */ + buf[4] = 0; + buf[5] = 0; + buf[6] = 2; + buf[7] = 0; + sc->result = GOOD; + break; + + case MODE_SENSE: + printk("MODE_SENSE\n"); + break; + + case START_STOP: + printk("START_STOP\n"); + break; + + default: + panic("simscsi: unknown SCSI command %u\n", sc->cmnd[0]); + } + } + if (sc->result == DID_BAD_TARGET) { + sc->result |= DRIVER_SENSE << 24; + sc->sense_buffer[0] = 0x70; + sc->sense_buffer[2] = 0x00; + } + if (atomic_read(&num_reqs) >= SIMSCSI_REQ_QUEUE_LEN) { + panic("Attempt to queue command while command is pending!!"); + } + atomic_inc(&num_reqs); + queue[wr].sc = sc; + wr = (wr + 1) % SIMSCSI_REQ_QUEUE_LEN; + +#if 0 + if (!timer_pending(&disk_timer)) { + disk_timer.expires = jiffies; + add_timer(&disk_timer); + } +#else + tasklet_schedule(&simscsi_tasklet); +#endif + return 0; +} + + +static Scsi_Host_Template driver_template = SIMSCSI; + +#include "scsi_module.c" diff -Nru a/arch/ia64/hp/sim/simscsi.h b/arch/ia64/hp/sim/simscsi.h --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/arch/ia64/hp/sim/simscsi.h Wed Oct 8 09:07:02 2003 @@ -0,0 +1,39 @@ +/* + * Simulated SCSI driver. + * + * Copyright (C) 1999 Hewlett-Packard Co + * Copyright (C) 1999 David Mosberger-Tang + */ +#ifndef SIMSCSI_H +#define SIMSCSI_H + +#define SIMSCSI_REQ_QUEUE_LEN 64 + +#define DEFAULT_SIMSCSI_ROOT "/var/ski-disks/sd" + +extern int simscsi_detect (Scsi_Host_Template *); +extern int simscsi_release (struct Scsi_Host *); +extern const char *simscsi_info (struct Scsi_Host *); +extern int simscsi_queuecommand (Scsi_Cmnd *, void (*done)(Scsi_Cmnd *)); +extern int simscsi_abort (Scsi_Cmnd *); +extern int simscsi_reset (Scsi_Cmnd *, unsigned int); +extern int simscsi_biosparam (Disk *, kdev_t, int[]); + +#define SIMSCSI { \ + detect: simscsi_detect, \ + release: simscsi_release, \ + info: simscsi_info, \ + queuecommand: simscsi_queuecommand, \ + abort: simscsi_abort, \ + reset: simscsi_reset, \ + bios_param: simscsi_biosparam, \ + can_queue: SIMSCSI_REQ_QUEUE_LEN, \ + this_id: -1, \ + sg_tablesize: SG_ALL, \ + cmd_per_lun: SIMSCSI_REQ_QUEUE_LEN, \ + present: 0, \ + unchecked_isa_dma: 0, \ + use_clustering: DISABLE_CLUSTERING \ +} + +#endif /* SIMSCSI_H */ diff -Nru a/arch/ia64/hp/sim/simserial.c b/arch/ia64/hp/sim/simserial.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/arch/ia64/hp/sim/simserial.c Wed Oct 8 09:07:02 2003 @@ -0,0 +1,1095 @@ +/* + * Simulated Serial Driver (fake serial) + * + * This driver is mostly used for bringup purposes and will go away. + * It has a strong dependency on the system console. All outputs + * are rerouted to the same facility as the one used by printk which, in our + * case means sys_sim.c console (goes via the simulator). The code hereafter + * is completely leveraged from the serial.c driver. + * + * Copyright (C) 1999-2000, 2002 Hewlett-Packard Co + * Stephane Eranian + * David Mosberger-Tang + * + * 02/04/00 D. Mosberger Merged in serial.c bug fixes in rs_close(). + * 02/25/00 D. Mosberger Synced up with 2.3.99pre-5 version of serial.c. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#undef SIMSERIAL_DEBUG /* define this to get some debug information */ + +#define KEYBOARD_INTR 3 /* must match with simulator! */ + +#define NR_PORTS 1 /* only one port for now */ +#define SERIAL_INLINE 1 + +#ifdef SERIAL_INLINE +#define _INLINE_ inline +#endif + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#define IRQ_T(info) ((info->flags & ASYNC_SHARE_IRQ) ? SA_SHIRQ : SA_INTERRUPT) + +#define SSC_GETCHAR 21 + +extern long ia64_ssc (long, long, long, long, int); +extern void ia64_ssc_connect_irq (long intr, long irq); + +static char *serial_name = "SimSerial driver"; +static char *serial_version = "0.6"; + +/* + * This has been extracted from asm/serial.h. We need one eventually but + * I don't know exactly what we're going to put in it so just fake one + * for now. + */ +#define BASE_BAUD ( 1843200 / 16 ) + +#define STD_COM_FLAGS (ASYNC_BOOT_AUTOCONF | ASYNC_SKIP_TEST) + +/* + * Most of the values here are meaningless to this particular driver. + * However some values must be preserved for the code (leveraged from serial.c + * to work correctly). + * port must not be 0 + * type must not be UNKNOWN + * So I picked arbitrary (guess from where?) values instead + */ +static struct serial_state rs_table[NR_PORTS]={ + /* UART CLK PORT IRQ FLAGS */ + { 0, BASE_BAUD, 0x3F8, 0, STD_COM_FLAGS,0,PORT_16550 } /* ttyS0 */ +}; + +/* + * Just for the fun of it ! + */ +static struct serial_uart_config uart_config[] = { + { "unknown", 1, 0 }, + { "8250", 1, 0 }, + { "16450", 1, 0 }, + { "16550", 1, 0 }, + { "16550A", 16, UART_CLEAR_FIFO | UART_USE_FIFO }, + { "cirrus", 1, 0 }, + { "ST16650", 1, UART_CLEAR_FIFO | UART_STARTECH }, + { "ST16650V2", 32, UART_CLEAR_FIFO | UART_USE_FIFO | + UART_STARTECH }, + { "TI16750", 64, UART_CLEAR_FIFO | UART_USE_FIFO}, + { 0, 0} +}; + +static struct tty_driver serial_driver, callout_driver; +static int serial_refcount; + +static struct async_struct *IRQ_ports[NR_IRQS]; +static struct tty_struct *serial_table[NR_PORTS]; +static struct termios *serial_termios[NR_PORTS]; +static struct termios *serial_termios_locked[NR_PORTS]; + +static struct console *console; + +static unsigned char *tmp_buf; +static DECLARE_MUTEX(tmp_buf_sem); + +extern struct console *console_drivers; /* from kernel/printk.c */ + +/* + * ------------------------------------------------------------ + * rs_stop() and rs_start() + * + * This routines are called before setting or resetting tty->stopped. + * They enable or disable transmitter interrupts, as necessary. + * ------------------------------------------------------------ + */ +static void rs_stop(struct tty_struct *tty) +{ +#ifdef SIMSERIAL_DEBUG + printk("rs_stop: tty->stopped=%d tty->hw_stopped=%d tty->flow_stopped=%d\n", + tty->stopped, tty->hw_stopped, tty->flow_stopped); +#endif + +} + +static void rs_start(struct tty_struct *tty) +{ +#if SIMSERIAL_DEBUG + printk("rs_start: tty->stopped=%d tty->hw_stopped=%d tty->flow_stopped=%d\n", + tty->stopped, tty->hw_stopped, tty->flow_stopped); +#endif +} + +static void receive_chars(struct tty_struct *tty, struct pt_regs *regs) +{ + unsigned char ch; + static unsigned char seen_esc = 0; + + while ( (ch = ia64_ssc(0, 0, 0, 0, SSC_GETCHAR)) ) { + if ( ch == 27 && seen_esc == 0 ) { + seen_esc = 1; + continue; + } else { + if ( seen_esc==1 && ch == 'O' ) { + seen_esc = 2; + continue; + } else if ( seen_esc == 2 ) { + if ( ch == 'P' ) show_state(); /* F1 key */ + if ( ch == 'Q' ) show_buffers(); /* F2 key */ + seen_esc = 0; + continue; + } + } + seen_esc = 0; + if (tty->flip.count >= TTY_FLIPBUF_SIZE) break; + + *tty->flip.char_buf_ptr = ch; + + *tty->flip.flag_buf_ptr = 0; + + tty->flip.flag_buf_ptr++; + tty->flip.char_buf_ptr++; + tty->flip.count++; + } + tty_flip_buffer_push(tty); +} + +/* + * This is the serial driver's interrupt routine for a single port + */ +static void rs_interrupt_single(int irq, void *dev_id, struct pt_regs * regs) +{ + struct async_struct * info; + + /* + * I don't know exactly why they don't use the dev_id opaque data + * pointer instead of this extra lookup table + */ + info = IRQ_ports[irq]; + if (!info || !info->tty) { + printk("simrs_interrupt_single: info|tty=0 info=%p problem\n", info); + return; + } + /* + * pretty simple in our case, because we only get interrupts + * on inbound traffic + */ + receive_chars(info->tty, regs); +} + +/* + * ------------------------------------------------------------------- + * Here ends the serial interrupt routines. + * ------------------------------------------------------------------- + */ + +#if 0 +/* + * not really used in our situation so keep them commented out for now + */ +static DECLARE_TASK_QUEUE(tq_serial); /* used to be at the top of the file */ +static void do_serial_bh(void) +{ + run_task_queue(&tq_serial); + printk("do_serial_bh: called\n"); +} +#endif + +static void do_softint(void *private_) +{ + printk("simserial: do_softint called\n"); +} + +static void rs_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + unsigned long flags; + + if (!tty || !info->xmit.buf) return; + + save_flags(flags); cli(); + if (CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE) == 0) { + restore_flags(flags); + return; + } + info->xmit.buf[info->xmit.head] = ch; + info->xmit.head = (info->xmit.head + 1) & (SERIAL_XMIT_SIZE-1); + restore_flags(flags); +} + +static _INLINE_ void transmit_chars(struct async_struct *info, int *intr_done) +{ + int count; + unsigned long flags; + + save_flags(flags); cli(); + + if (info->x_char) { + char c = info->x_char; + + console->write(console, &c, 1); + + info->state->icount.tx++; + info->x_char = 0; + + goto out; + } + + if (info->xmit.head == info->xmit.tail || info->tty->stopped || info->tty->hw_stopped) { +#ifdef SIMSERIAL_DEBUG + printk("transmit_chars: head=%d, tail=%d, stopped=%d\n", + info->xmit.head, info->xmit.tail, info->tty->stopped); +#endif + goto out; + } + /* + * We removed the loop and try to do it in to chunks. We need + * 2 operations maximum because it's a ring buffer. + * + * First from current to tail if possible. + * Then from the beginning of the buffer until necessary + */ + + count = MIN(CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE), + SERIAL_XMIT_SIZE - info->xmit.tail); + console->write(console, info->xmit.buf+info->xmit.tail, count); + + info->xmit.tail = (info->xmit.tail+count) & (SERIAL_XMIT_SIZE-1); + + /* + * We have more at the beginning of the buffer + */ + count = CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); + if (count) { + console->write(console, info->xmit.buf, count); + info->xmit.tail += count; + } +out: + restore_flags(flags); +} + +static void rs_flush_chars(struct tty_struct *tty) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + + if (info->xmit.head == info->xmit.tail || tty->stopped || tty->hw_stopped || + !info->xmit.buf) + return; + + transmit_chars(info, NULL); +} + + +static int rs_write(struct tty_struct * tty, int from_user, + const unsigned char *buf, int count) +{ + int c, ret = 0; + struct async_struct *info = (struct async_struct *)tty->driver_data; + unsigned long flags; + + if (!tty || !info->xmit.buf || !tmp_buf) return 0; + + save_flags(flags); + if (from_user) { + down(&tmp_buf_sem); + while (1) { + int c1; + c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) + break; + + c -= copy_from_user(tmp_buf, buf, c); + if (!c) { + if (!ret) + ret = -EFAULT; + break; + } + cli(); + c1 = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); + if (c1 < c) + c = c1; + memcpy(info->xmit.buf + info->xmit.head, tmp_buf, c); + info->xmit.head = ((info->xmit.head + c) & + (SERIAL_XMIT_SIZE-1)); + restore_flags(flags); + buf += c; + count -= c; + ret += c; + } + up(&tmp_buf_sem); + } else { + cli(); + while (1) { + c = CIRC_SPACE_TO_END(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); + if (count < c) + c = count; + if (c <= 0) { + break; + } + memcpy(info->xmit.buf + info->xmit.head, buf, c); + info->xmit.head = ((info->xmit.head + c) & + (SERIAL_XMIT_SIZE-1)); + buf += c; + count -= c; + ret += c; + } + restore_flags(flags); + } + /* + * Hey, we transmit directly from here in our case + */ + if (CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE) + && !tty->stopped && !tty->hw_stopped) { + transmit_chars(info, NULL); + } + return ret; +} + +static int rs_write_room(struct tty_struct *tty) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + + return CIRC_SPACE(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); +} + +static int rs_chars_in_buffer(struct tty_struct *tty) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + + return CIRC_CNT(info->xmit.head, info->xmit.tail, SERIAL_XMIT_SIZE); +} + +static void rs_flush_buffer(struct tty_struct *tty) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + unsigned long flags; + + save_flags(flags); cli(); + info->xmit.head = info->xmit.tail = 0; + restore_flags(flags); + + wake_up_interruptible(&tty->write_wait); + + if ((tty->flags & (1 << TTY_DO_WRITE_WAKEUP)) && + tty->ldisc.write_wakeup) + (tty->ldisc.write_wakeup)(tty); +} + +/* + * This function is used to send a high-priority XON/XOFF character to + * the device + */ +static void rs_send_xchar(struct tty_struct *tty, char ch) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + + info->x_char = ch; + if (ch) { + /* + * I guess we could call console->write() directly but + * let's do that for now. + */ + transmit_chars(info, NULL); + } +} + +/* + * ------------------------------------------------------------ + * rs_throttle() + * + * This routine is called by the upper-layer tty layer to signal that + * incoming characters should be throttled. + * ------------------------------------------------------------ + */ +static void rs_throttle(struct tty_struct * tty) +{ + if (I_IXOFF(tty)) rs_send_xchar(tty, STOP_CHAR(tty)); + + printk("simrs_throttle called\n"); +} + +static void rs_unthrottle(struct tty_struct * tty) +{ + struct async_struct *info = (struct async_struct *)tty->driver_data; + + if (I_IXOFF(tty)) { + if (info->x_char) + info->x_char = 0; + else + rs_send_xchar(tty, START_CHAR(tty)); + } + printk("simrs_unthrottle called\n"); +} + +/* + * rs_break() --- routine which turns the break handling on or off + */ +static void rs_break(struct tty_struct *tty, int break_state) +{ +} + +static int rs_ioctl(struct tty_struct *tty, struct file * file, + unsigned int cmd, unsigned long arg) +{ + if ((cmd != TIOCGSERIAL) && (cmd != TIOCSSERIAL) && + (cmd != TIOCSERCONFIG) && (cmd != TIOCSERGSTRUCT) && + (cmd != TIOCMIWAIT) && (cmd != TIOCGICOUNT)) { + if (tty->flags & (1 << TTY_IO_ERROR)) + return -EIO; + } + + switch (cmd) { + case TIOCMGET: + printk("rs_ioctl: TIOCMGET called\n"); + return -EINVAL; + case TIOCMBIS: + case TIOCMBIC: + case TIOCMSET: + printk("rs_ioctl: TIOCMBIS/BIC/SET called\n"); + return -EINVAL; + case TIOCGSERIAL: + printk("simrs_ioctl TIOCGSERIAL called\n"); + return 0; + case TIOCSSERIAL: + printk("simrs_ioctl TIOCSSERIAL called\n"); + return 0; + case TIOCSERCONFIG: + printk("rs_ioctl: TIOCSERCONFIG called\n"); + return -EINVAL; + + case TIOCSERGETLSR: /* Get line status register */ + printk("rs_ioctl: TIOCSERGETLSR called\n"); + return -EINVAL; + + case TIOCSERGSTRUCT: + printk("rs_ioctl: TIOCSERGSTRUCT called\n"); +#if 0 + if (copy_to_user((struct async_struct *) arg, + info, sizeof(struct async_struct))) + return -EFAULT; +#endif + return 0; + + /* + * Wait for any of the 4 modem inputs (DCD,RI,DSR,CTS) to change + * - mask passed in arg for lines of interest + * (use |'ed TIOCM_RNG/DSR/CD/CTS for masking) + * Caller should use TIOCGICOUNT to see which one it was + */ + case TIOCMIWAIT: + printk("rs_ioctl: TIOCMIWAIT: called\n"); + return 0; + /* + * Get counter of input serial line interrupts (DCD,RI,DSR,CTS) + * Return: write counters to the user passed counter struct + * NB: both 1->0 and 0->1 transitions are counted except for + * RI where only 0->1 is counted. + */ + case TIOCGICOUNT: + printk("rs_ioctl: TIOCGICOUNT called\n"); + return 0; + + case TIOCSERGWILD: + case TIOCSERSWILD: + /* "setserial -W" is called in Debian boot */ + printk ("TIOCSER?WILD ioctl obsolete, ignored.\n"); + return 0; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +#define RELEVANT_IFLAG(iflag) (iflag & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK)) + +static void rs_set_termios(struct tty_struct *tty, struct termios *old_termios) +{ + unsigned int cflag = tty->termios->c_cflag; + + if ( (cflag == old_termios->c_cflag) + && ( RELEVANT_IFLAG(tty->termios->c_iflag) + == RELEVANT_IFLAG(old_termios->c_iflag))) + return; + + + /* Handle turning off CRTSCTS */ + if ((old_termios->c_cflag & CRTSCTS) && + !(tty->termios->c_cflag & CRTSCTS)) { + tty->hw_stopped = 0; + rs_start(tty); + } +} +/* + * This routine will shutdown a serial port; interrupts are disabled, and + * DTR is dropped if the hangup on close termio flag is on. + */ +static void shutdown(struct async_struct * info) +{ + unsigned long flags; + struct serial_state *state; + int retval; + + if (!(info->flags & ASYNC_INITIALIZED)) return; + + state = info->state; + +#ifdef SIMSERIAL_DEBUG + printk("Shutting down serial port %d (irq %d)....", info->line, + state->irq); +#endif + + save_flags(flags); cli(); /* Disable interrupts */ + + /* + * First unlink the serial port from the IRQ chain... + */ + if (info->next_port) + info->next_port->prev_port = info->prev_port; + if (info->prev_port) + info->prev_port->next_port = info->next_port; + else + IRQ_ports[state->irq] = info->next_port; + + /* + * Free the IRQ, if necessary + */ + if (state->irq && (!IRQ_ports[state->irq] || + !IRQ_ports[state->irq]->next_port)) { + if (IRQ_ports[state->irq]) { + free_irq(state->irq, NULL); + retval = request_irq(state->irq, rs_interrupt_single, + IRQ_T(info), "serial", NULL); + + if (retval) + printk("serial shutdown: request_irq: error %d" + " Couldn't reacquire IRQ.\n", retval); + } else + free_irq(state->irq, NULL); + } + + if (info->xmit.buf) { + free_page((unsigned long) info->xmit.buf); + info->xmit.buf = 0; + } + + if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); + + info->flags &= ~ASYNC_INITIALIZED; + restore_flags(flags); +} + +/* + * ------------------------------------------------------------ + * rs_close() + * + * This routine is called when the serial port gets closed. First, we + * wait for the last remaining data to be sent. Then, we unlink its + * async structure from the interrupt chain if necessary, and we free + * that IRQ if nothing is left in the chain. + * ------------------------------------------------------------ + */ +static void rs_close(struct tty_struct *tty, struct file * filp) +{ + struct async_struct * info = (struct async_struct *)tty->driver_data; + struct serial_state *state; + unsigned long flags; + + if (!info ) return; + + state = info->state; + + save_flags(flags); cli(); + + if (tty_hung_up_p(filp)) { +#ifdef SIMSERIAL_DEBUG + printk("rs_close: hung_up\n"); +#endif + MOD_DEC_USE_COUNT; + restore_flags(flags); + return; + } +#ifdef SIMSERIAL_DEBUG + printk("rs_close ttys%d, count = %d\n", info->line, state->count); +#endif + if ((tty->count == 1) && (state->count != 1)) { + /* + * Uh, oh. tty->count is 1, which means that the tty + * structure will be freed. state->count should always + * be one in these conditions. If it's greater than + * one, we've got real problems, since it means the + * serial port won't be shutdown. + */ + printk("rs_close: bad serial port count; tty->count is 1, " + "state->count is %d\n", state->count); + state->count = 1; + } + if (--state->count < 0) { + printk("rs_close: bad serial port count for ttys%d: %d\n", + info->line, state->count); + state->count = 0; + } + if (state->count) { + MOD_DEC_USE_COUNT; + restore_flags(flags); + return; + } + info->flags |= ASYNC_CLOSING; + restore_flags(flags); + + /* + * Now we wait for the transmit buffer to clear; and we notify + * the line discipline to only process XON/XOFF characters. + */ + shutdown(info); + if (tty->driver.flush_buffer) tty->driver.flush_buffer(tty); + if (tty->ldisc.flush_buffer) tty->ldisc.flush_buffer(tty); + info->event = 0; + info->tty = 0; + if (info->blocked_open) { + if (info->close_delay) { + current->state = TASK_INTERRUPTIBLE; + schedule_timeout(info->close_delay); + } + wake_up_interruptible(&info->open_wait); + } + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE|ASYNC_CLOSING); + wake_up_interruptible(&info->close_wait); + MOD_DEC_USE_COUNT; +} + +/* + * rs_wait_until_sent() --- wait until the transmitter is empty + */ +static void rs_wait_until_sent(struct tty_struct *tty, int timeout) +{ +} + + +/* + * rs_hangup() --- called by tty_hangup() when a hangup is signaled. + */ +static void rs_hangup(struct tty_struct *tty) +{ + struct async_struct * info = (struct async_struct *)tty->driver_data; + struct serial_state *state = info->state; + +#ifdef SIMSERIAL_DEBUG + printk("rs_hangup: called\n"); +#endif + + state = info->state; + + rs_flush_buffer(tty); + if (info->flags & ASYNC_CLOSING) + return; + shutdown(info); + + info->event = 0; + state->count = 0; + info->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CALLOUT_ACTIVE); + info->tty = 0; + wake_up_interruptible(&info->open_wait); +} + + +static int get_async_struct(int line, struct async_struct **ret_info) +{ + struct async_struct *info; + struct serial_state *sstate; + + sstate = rs_table + line; + sstate->count++; + if (sstate->info) { + *ret_info = sstate->info; + return 0; + } + info = kmalloc(sizeof(struct async_struct), GFP_KERNEL); + if (!info) { + sstate->count--; + return -ENOMEM; + } + memset(info, 0, sizeof(struct async_struct)); + init_waitqueue_head(&info->open_wait); + init_waitqueue_head(&info->close_wait); + init_waitqueue_head(&info->delta_msr_wait); + info->magic = SERIAL_MAGIC; + info->port = sstate->port; + info->flags = sstate->flags; + info->xmit_fifo_size = sstate->xmit_fifo_size; + info->line = line; + info->tqueue.routine = do_softint; + info->tqueue.data = info; + info->state = sstate; + if (sstate->info) { + kfree(info); + *ret_info = sstate->info; + return 0; + } + *ret_info = sstate->info = info; + return 0; +} + +static int +startup(struct async_struct *info) +{ + unsigned long flags; + int retval=0; + void (*handler)(int, void *, struct pt_regs *); + struct serial_state *state= info->state; + unsigned long page; + + page = get_free_page(GFP_KERNEL); + if (!page) + return -ENOMEM; + + save_flags(flags); cli(); + + if (info->flags & ASYNC_INITIALIZED) { + free_page(page); + goto errout; + } + + if (!state->port || !state->type) { + if (info->tty) set_bit(TTY_IO_ERROR, &info->tty->flags); + free_page(page); + goto errout; + } + if (info->xmit.buf) + free_page(page); + else + info->xmit.buf = (unsigned char *) page; + +#ifdef SIMSERIAL_DEBUG + printk("startup: ttys%d (irq %d)...", info->line, state->irq); +#endif + + /* + * Allocate the IRQ if necessary + */ + if (state->irq && (!IRQ_ports[state->irq] || + !IRQ_ports[state->irq]->next_port)) { + if (IRQ_ports[state->irq]) { + retval = -EBUSY; + goto errout; + } else + handler = rs_interrupt_single; + + retval = request_irq(state->irq, handler, IRQ_T(info), + "simserial", NULL); + if (retval) { + if (capable(CAP_SYS_ADMIN)) { + if (info->tty) + set_bit(TTY_IO_ERROR, + &info->tty->flags); + retval = 0; + } + goto errout; + } + } + + /* + * Insert serial port into IRQ chain. + */ + info->prev_port = 0; + info->next_port = IRQ_ports[state->irq]; + if (info->next_port) + info->next_port->prev_port = info; + IRQ_ports[state->irq] = info; + + if (info->tty) clear_bit(TTY_IO_ERROR, &info->tty->flags); + + info->xmit.head = info->xmit.tail = 0; + +#if 0 + /* + * Set up serial timers... + */ + timer_table[RS_TIMER].expires = jiffies + 2*HZ/100; + timer_active |= 1 << RS_TIMER; +#endif + + /* + * Set up the tty->alt_speed kludge + */ + if (info->tty) { + if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_HI) + info->tty->alt_speed = 57600; + if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_VHI) + info->tty->alt_speed = 115200; + if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_SHI) + info->tty->alt_speed = 230400; + if ((info->flags & ASYNC_SPD_MASK) == ASYNC_SPD_WARP) + info->tty->alt_speed = 460800; + } + + info->flags |= ASYNC_INITIALIZED; + restore_flags(flags); + return 0; + +errout: + restore_flags(flags); + return retval; +} + + +/* + * This routine is called whenever a serial port is opened. It + * enables interrupts for a serial port, linking in its async structure into + * the IRQ chain. It also performs the serial-specific + * initialization for the tty structure. + */ +static int rs_open(struct tty_struct *tty, struct file * filp) +{ + struct async_struct *info; + int retval, line; + unsigned long page; + + MOD_INC_USE_COUNT; + line = MINOR(tty->device) - tty->driver.minor_start; + if ((line < 0) || (line >= NR_PORTS)) { + MOD_DEC_USE_COUNT; + return -ENODEV; + } + retval = get_async_struct(line, &info); + if (retval) { + MOD_DEC_USE_COUNT; + return retval; + } + tty->driver_data = info; + info->tty = tty; + +#ifdef SIMSERIAL_DEBUG + printk("rs_open %s%d, count = %d\n", tty->driver.name, info->line, + info->state->count); +#endif + info->tty->low_latency = (info->flags & ASYNC_LOW_LATENCY) ? 1 : 0; + + if (!tmp_buf) { + page = get_free_page(GFP_KERNEL); + if (!page) { + /* MOD_DEC_USE_COUNT; "info->tty" will cause this? */ + return -ENOMEM; + } + if (tmp_buf) + free_page(page); + else + tmp_buf = (unsigned char *) page; + } + + /* + * If the port is the middle of closing, bail out now + */ + if (tty_hung_up_p(filp) || + (info->flags & ASYNC_CLOSING)) { + if (info->flags & ASYNC_CLOSING) + interruptible_sleep_on(&info->close_wait); + /* MOD_DEC_USE_COUNT; "info->tty" will cause this? */ +#ifdef SERIAL_DO_RESTART + return ((info->flags & ASYNC_HUP_NOTIFY) ? + -EAGAIN : -ERESTARTSYS); +#else + return -EAGAIN; +#endif + } + + /* + * Start up serial port + */ + retval = startup(info); + if (retval) { + /* MOD_DEC_USE_COUNT; "info->tty" will cause this? */ + return retval; + } + + if ((info->state->count == 1) && + (info->flags & ASYNC_SPLIT_TERMIOS)) { + if (tty->driver.subtype == SERIAL_TYPE_NORMAL) + *tty->termios = info->state->normal_termios; + else + *tty->termios = info->state->callout_termios; + } + + /* + * figure out which console to use (should be one already) + */ + console = console_drivers; + while (console) { + if ((console->flags & CON_ENABLED) && console->write) break; + console = console->next; + } + + info->session = current->session; + info->pgrp = current->pgrp; + +#ifdef SIMSERIAL_DEBUG + printk("rs_open ttys%d successful\n", info->line); +#endif + return 0; +} + +/* + * /proc fs routines.... + */ + +static inline int line_info(char *buf, struct serial_state *state) +{ + return sprintf(buf, "%d: uart:%s port:%lX irq:%d\n", + state->line, uart_config[state->type].name, + state->port, state->irq); +} + +static int rs_read_proc(char *page, char **start, off_t off, int count, + int *eof, void *data) +{ + int i, len = 0, l; + off_t begin = 0; + + len += sprintf(page, "simserinfo:1.0 driver:%s\n", serial_version); + for (i = 0; i < NR_PORTS && len < 4000; i++) { + l = line_info(page + len, &rs_table[i]); + len += l; + if (len+begin > off+count) + goto done; + if (len+begin < off) { + begin += len; + len = 0; + } + } + *eof = 1; +done: + if (off >= len+begin) + return 0; + *start = page + (begin-off); + return ((count < begin+len-off) ? count : begin+len-off); +} + +/* + * --------------------------------------------------------------------- + * rs_init() and friends + * + * rs_init() is called at boot-time to initialize the serial driver. + * --------------------------------------------------------------------- + */ + +/* + * This routine prints out the appropriate serial driver version + * number, and identifies which options were configured into this + * driver. + */ +static inline void show_serial_version(void) +{ + printk(KERN_INFO "%s version %s with", serial_name, serial_version); + printk(" no serial options enabled\n"); +} + +/* + * The serial driver boot-time initialization code! + */ +static int __init +simrs_init (void) +{ + int i; + struct serial_state *state; + + show_serial_version(); + + /* Initialize the tty_driver structure */ + + memset(&serial_driver, 0, sizeof(struct tty_driver)); + serial_driver.magic = TTY_DRIVER_MAGIC; + serial_driver.driver_name = "simserial"; + serial_driver.name = "ttyS"; + serial_driver.major = TTY_MAJOR; + serial_driver.minor_start = 64; + serial_driver.num = 1; + serial_driver.type = TTY_DRIVER_TYPE_SERIAL; + serial_driver.subtype = SERIAL_TYPE_NORMAL; + serial_driver.init_termios = tty_std_termios; + serial_driver.init_termios.c_cflag = + B9600 | CS8 | CREAD | HUPCL | CLOCAL; + serial_driver.flags = TTY_DRIVER_REAL_RAW; + serial_driver.refcount = &serial_refcount; + serial_driver.table = serial_table; + serial_driver.termios = serial_termios; + serial_driver.termios_locked = serial_termios_locked; + + serial_driver.open = rs_open; + serial_driver.close = rs_close; + serial_driver.write = rs_write; + serial_driver.put_char = rs_put_char; + serial_driver.flush_chars = rs_flush_chars; + serial_driver.write_room = rs_write_room; + serial_driver.chars_in_buffer = rs_chars_in_buffer; + serial_driver.flush_buffer = rs_flush_buffer; + serial_driver.ioctl = rs_ioctl; + serial_driver.throttle = rs_throttle; + serial_driver.unthrottle = rs_unthrottle; + serial_driver.send_xchar = rs_send_xchar; + serial_driver.set_termios = rs_set_termios; + serial_driver.stop = rs_stop; + serial_driver.start = rs_start; + serial_driver.hangup = rs_hangup; + serial_driver.break_ctl = rs_break; + serial_driver.wait_until_sent = rs_wait_until_sent; + serial_driver.read_proc = rs_read_proc; + + /* + * Let's have a little bit of fun ! + */ + for (i = 0, state = rs_table; i < NR_PORTS; i++,state++) { + + if (state->type == PORT_UNKNOWN) continue; + + if (!state->irq) { + state->irq = ia64_alloc_irq(); + ia64_ssc_connect_irq(KEYBOARD_INTR, state->irq); + } + + printk(KERN_INFO "ttyS%02d at 0x%04lx (irq = %d) is a %s\n", + state->line, + state->port, state->irq, + uart_config[state->type].name); + } + /* + * The callout device is just like normal device except for + * major number and the subtype code. + */ + callout_driver = serial_driver; + callout_driver.name = "cua"; + callout_driver.major = TTYAUX_MAJOR; + callout_driver.subtype = SERIAL_TYPE_CALLOUT; + callout_driver.read_proc = 0; + callout_driver.proc_entry = 0; + + if (tty_register_driver(&serial_driver)) + panic("Couldn't register simserial driver\n"); + + if (tty_register_driver(&callout_driver)) + panic("Couldn't register callout driver\n"); + + return 0; +} + +#ifndef MODULE +__initcall(simrs_init); +#endif